/********************************************************************
 * This file includes functions that are used to organize memories 
 * in the top module of FPGA fabric
 *******************************************************************/
#include <cmath>
#include <limits>

/* Headers from vtrutil library */
#include "vtr_assert.h"
#include "vtr_log.h"
#include "vtr_time.h"

/* Headers from vpr library */
#include "vpr_utils.h"

/* Headers from openfpgashell library */
#include "command_exit_codes.h"

#include "rr_gsb_utils.h"
#include "openfpga_reserved_words.h"
#include "openfpga_naming.h"

#include "memory_utils.h"
#include "decoder_library_utils.h"
#include "module_manager_utils.h"
#include "memory_bank_utils.h"
#include "build_memory_modules.h"
#include "build_decoder_modules.h"
#include "memory_bank_shift_register_banks.h"
#include "build_top_module_memory_bank.h"

/* begin namespace openfpga */
namespace openfpga {

/********************************************************************
 * Connect all the memory modules under the parent module in a chain
 * 
 *                +--------+    +--------+            +--------+
 *  ccff_head --->| Memory |--->| Memory |--->... --->| Memory |----> ccff_tail
 *                | Module |    | Module |            | Module |
 *                |   [0]  |    |   [1]  |            |  [N-1] |             
 *                +--------+    +--------+            +--------+
 *  For the 1st memory module:
 *    net source is the configuration chain head of the primitive module
 *    net sink is the configuration chain head of the next memory module
 *
 *  For the rest of memory modules:
 *    net source is the configuration chain tail of the previous memory module
 *    net sink is the configuration chain head of the next memory module
 *
 *  Note that:
 *    This function is designed for memory modules ONLY!
 *    Do not use it to replace the 
 *      add_module_nets_cmos_memory_chain_config_bus() !!!
 *********************************************************************/
static 
void add_module_nets_to_ql_memory_bank_shift_register_module(ModuleManager& module_manager,
                                                             const ModuleId& parent_module,
                                                             const CircuitLibrary& circuit_lib,
                                                             const CircuitPortId& model_input_port,
                                                             const CircuitPortId& model_output_port,
                                                             const std::string& chain_head_port_name,
                                                             const std::string& chain_tail_port_name) {
  for (size_t mem_index = 0; mem_index < module_manager.configurable_children(parent_module).size(); ++mem_index) {
    ModuleId net_src_module_id;
    size_t net_src_instance_id;
    ModulePortId net_src_port_id;

    ModuleId net_sink_module_id;
    size_t net_sink_instance_id;
    ModulePortId net_sink_port_id;

    if (0 == mem_index) {
      /* Find the port name of configuration chain head */
      std::string src_port_name = chain_head_port_name;
      net_src_module_id = parent_module; 
      net_src_instance_id = 0;
      net_src_port_id = module_manager.find_module_port(net_src_module_id, src_port_name); 

      /* Find the port name of next memory module */
      std::string sink_port_name = circuit_lib.port_prefix(model_input_port);
      net_sink_module_id = module_manager.configurable_children(parent_module)[mem_index]; 
      net_sink_instance_id = module_manager.configurable_child_instances(parent_module)[mem_index];
      net_sink_port_id = module_manager.find_module_port(net_sink_module_id, sink_port_name); 
    } else {
      /* Find the port name of previous memory module */
      std::string src_port_name = circuit_lib.port_prefix(model_output_port);
      net_src_module_id = module_manager.configurable_children(parent_module)[mem_index - 1]; 
      net_src_instance_id = module_manager.configurable_child_instances(parent_module)[mem_index - 1];
      net_src_port_id = module_manager.find_module_port(net_src_module_id, src_port_name); 

      /* Find the port name of next memory module */
      std::string sink_port_name = circuit_lib.port_prefix(model_input_port);
      net_sink_module_id = module_manager.configurable_children(parent_module)[mem_index]; 
      net_sink_instance_id = module_manager.configurable_child_instances(parent_module)[mem_index];
      net_sink_port_id = module_manager.find_module_port(net_sink_module_id, sink_port_name); 
    }

    /* Get the pin id for source port */
    BasicPort net_src_port = module_manager.module_port(net_src_module_id, net_src_port_id); 
    /* Get the pin id for sink port */
    BasicPort net_sink_port = module_manager.module_port(net_sink_module_id, net_sink_port_id); 
    /* Port sizes of source and sink should match */
    VTR_ASSERT(net_src_port.get_width() == net_sink_port.get_width());
    
    /* Create a net for each pin */
    for (size_t pin_id = 0; pin_id < net_src_port.pins().size(); ++pin_id) {
      /* Create a net and add source and sink to it */
      ModuleNetId net = create_module_source_pin_net(module_manager, parent_module, net_src_module_id, net_src_instance_id, net_src_port_id, net_src_port.pins()[pin_id]);
      /* Add net sink */
      module_manager.add_module_net_sink(parent_module, net, net_sink_module_id, net_sink_instance_id, net_sink_port_id, net_sink_port.pins()[pin_id]);
    }
  }

  /* For the last memory module:
   *    net source is the configuration chain tail of the previous memory module
   *    net sink is the configuration chain tail of the primitive module
   */
  /* Find the port name of previous memory module */
  std::string src_port_name = circuit_lib.port_prefix(model_output_port);
  ModuleId net_src_module_id = module_manager.configurable_children(parent_module).back(); 
  size_t net_src_instance_id = module_manager.configurable_child_instances(parent_module).back();
  ModulePortId net_src_port_id = module_manager.find_module_port(net_src_module_id, src_port_name); 

  /* Find the port name of next memory module */
  std::string sink_port_name = chain_tail_port_name;
  ModuleId net_sink_module_id = parent_module; 
  size_t net_sink_instance_id = 0;
  ModulePortId net_sink_port_id = module_manager.find_module_port(net_sink_module_id, sink_port_name); 

  /* Get the pin id for source port */
  BasicPort net_src_port = module_manager.module_port(net_src_module_id, net_src_port_id); 
  /* Get the pin id for sink port */
  BasicPort net_sink_port = module_manager.module_port(net_sink_module_id, net_sink_port_id); 
  /* Port sizes of source and sink should match */
  VTR_ASSERT(net_src_port.get_width() == net_sink_port.get_width());
  
  /* Create a net for each pin */
  for (size_t pin_id = 0; pin_id < net_src_port.pins().size(); ++pin_id) {
    /* Create a net and add source and sink to it */
    ModuleNetId net = create_module_source_pin_net(module_manager, parent_module, net_src_module_id, net_src_instance_id, net_src_port_id, net_src_port.pins()[pin_id]);
    /* Add net sink */
    module_manager.add_module_net_sink(parent_module, net, net_sink_module_id, net_sink_instance_id, net_sink_port_id, net_sink_port.pins()[pin_id]);
  }
}

/*********************************************************************
 * BL shift register chain module organization
 *              
 *                +-------+    +-------+            +-------+
 *    chain   --->| CCFF  |--->| CCFF  |--->... --->| CCFF  |---->chain
 *  input&clock   |  [0]  |    |  [1]  |            | [N-1] |     output
 *                +-------+    +-------+            +-------+
 *                    |            |      ...           | config-memory output
 *                    v            v                    v
 *                +-----------------------------------------+
 *                |   BLs of configurable modules           |
 *
 ********************************************************************/
static 
ModuleId build_bl_shift_register_chain_module(ModuleManager& module_manager,
                                              const CircuitLibrary& circuit_lib,
                                              const std::string& module_name,
                                              const CircuitModelId& sram_model,
                                              const size_t& num_mems) {

  /* Get the input ports from the SRAM */
  std::vector<CircuitPortId> sram_input_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_INPUT, true);
  VTR_ASSERT(1 == sram_input_ports.size());

  /* Get the output ports from the SRAM */
  std::vector<CircuitPortId> sram_output_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_OUTPUT, true);
  VTR_ASSERT(1 == sram_output_ports.size());

  /* Get the BL ports from the SRAM */
  std::vector<CircuitPortId> sram_bl_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_BL, true);
  VTR_ASSERT(1 == sram_bl_ports.size());

  /* Create a module and add to the module manager */
  ModuleId mem_module = module_manager.add_module(module_name); 
  VTR_ASSERT(true == module_manager.valid_module_id(mem_module));

  /* Label module usage */
  module_manager.set_module_usage(mem_module, ModuleManager::MODULE_CONFIG);
  
  /* Add an input port, which is the head of configuration chain in the module */
  /* TODO: restriction!!!
   * consider only the first input of the CCFF model as the D port,
   * which will be connected to the head of the chain
   */
  BasicPort chain_head_port(BL_SHIFT_REGISTER_CHAIN_HEAD_NAME, 
                            circuit_lib.port_size(sram_input_ports[0]));
  module_manager.add_port(mem_module, chain_head_port, ModuleManager::MODULE_INPUT_PORT);

  /* Add an output port, which is the tail of configuration chain in the module */
  /* TODO: restriction!!!
   * consider only the first output of the CCFF model as the Q port,
   * which will be connected to the tail of the chain
   */
  BasicPort chain_tail_port(BL_SHIFT_REGISTER_CHAIN_TAIL_NAME, 
                            circuit_lib.port_size(sram_output_ports[0]));
  module_manager.add_port(mem_module, chain_tail_port, ModuleManager::MODULE_OUTPUT_PORT);

  /* Add the output ports to output BL signals */
  BasicPort chain_bl_port(BL_SHIFT_REGISTER_CHAIN_BL_OUT_NAME, 
                          num_mems);
  module_manager.add_port(mem_module, chain_bl_port, ModuleManager::MODULE_OUTPUT_PORT);

  /* Find the sram module in the module manager */
  ModuleId sram_mem_module = module_manager.find_module(circuit_lib.model_name(sram_model));

  /* Instanciate each submodule */
  for (size_t i = 0; i < num_mems; ++i) {
    size_t sram_mem_instance = module_manager.num_instance(mem_module, sram_mem_module);
    module_manager.add_child_module(mem_module, sram_mem_module);
    module_manager.add_configurable_child(mem_module, sram_mem_module, sram_mem_instance);

    /* Build module nets to wire bl outputs of sram modules to BL outputs of memory module */
    for (const auto& child_module_output_port : sram_bl_ports) {
      std::string chain_output_port_name = std::string(BL_SHIFT_REGISTER_CHAIN_BL_OUT_NAME); 
      add_module_output_nets_to_chain_mem_modules(module_manager, mem_module, 
                                                  chain_output_port_name, circuit_lib, 
                                                  child_module_output_port,
                                                  sram_mem_module, i, sram_mem_instance);
    }
  }

  /* Build module nets to wire the configuration chain */
  add_module_nets_to_ql_memory_bank_shift_register_module(module_manager, mem_module,
                                                          circuit_lib, sram_input_ports[0], sram_output_ports[0],
                                                          std::string(BL_SHIFT_REGISTER_CHAIN_HEAD_NAME),
                                                          std::string(BL_SHIFT_REGISTER_CHAIN_TAIL_NAME));

  /* Add global ports to the pb_module:
   * This is a much easier job after adding sub modules (instances), 
   * we just need to find all the global ports from the child modules and build a list of it
   */
  add_module_global_ports_from_child_modules(module_manager, mem_module);

  return mem_module;
}

/*********************************************************************
 * WL shift register chain module organization
 *              
 *                +-------+    +-------+            +-------+
 *    chain   --->| CCFF  |--->| CCFF  |--->... --->| CCFF  |---->chain
 *  input&clock   |  [0]  |    |  [1]  |            | [N-1] |     output
 *                +-------+    +-------+            +-------+
 *                    |            |      ...           | config-memory output
 *                    v            v                    v
 *                +-----------------------------------------+
 *                |   WL/WLRs of configurable modules       |
 *
 ********************************************************************/
static 
ModuleId build_wl_shift_register_chain_module(ModuleManager& module_manager,
                                              const CircuitLibrary& circuit_lib,
                                              const std::string& module_name,
                                              const CircuitModelId& sram_model,
                                              const size_t& num_mems) {

  /* Get the input ports from the SRAM */
  std::vector<CircuitPortId> sram_input_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_INPUT, true);
  VTR_ASSERT(1 == sram_input_ports.size());

  /* Get the output ports from the SRAM */
  std::vector<CircuitPortId> sram_output_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_OUTPUT, true);
  VTR_ASSERT(1 == sram_output_ports.size());

  /* Get the WL ports from the SRAM */
  std::vector<CircuitPortId> sram_wl_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_WL, true);
  VTR_ASSERT(1 == sram_wl_ports.size());

  /* Get the optional WLR ports from the SRAM */
  std::vector<CircuitPortId> sram_wlr_ports = circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_WLR, true);
  VTR_ASSERT(0 == sram_wlr_ports.size() || 1 == sram_wlr_ports.size());

  /* Create a module and add to the module manager */
  ModuleId mem_module = module_manager.add_module(module_name); 
  VTR_ASSERT(true == module_manager.valid_module_id(mem_module));

  /* Label module usage */
  module_manager.set_module_usage(mem_module, ModuleManager::MODULE_CONFIG);
  
  /* Add an input port, which is the head of configuration chain in the module */
  /* TODO: restriction!!!
   * consider only the first input of the CCFF model as the D port,
   * which will be connected to the head of the chain
   */
  BasicPort chain_head_port(WL_SHIFT_REGISTER_CHAIN_HEAD_NAME, 
                            circuit_lib.port_size(sram_input_ports[0]));
  module_manager.add_port(mem_module, chain_head_port, ModuleManager::MODULE_INPUT_PORT);

  /* Add an output port, which is the tail of configuration chain in the module */
  /* TODO: restriction!!!
   * consider only the first output of the CCFF model as the Q port,
   * which will be connected to the tail of the chain
   */
  BasicPort chain_tail_port(WL_SHIFT_REGISTER_CHAIN_TAIL_NAME, 
                            circuit_lib.port_size(sram_output_ports[0]));
  module_manager.add_port(mem_module, chain_tail_port, ModuleManager::MODULE_OUTPUT_PORT);

  /* Add the output ports to output BL/WL signals */
  BasicPort chain_wl_port(WL_SHIFT_REGISTER_CHAIN_WL_OUT_NAME, 
                          num_mems);
  module_manager.add_port(mem_module, chain_wl_port, ModuleManager::MODULE_OUTPUT_PORT);

  /* Add the output ports to output WLR signals */
  if (!sram_wlr_ports.empty()) {
    BasicPort chain_wlr_port(WL_SHIFT_REGISTER_CHAIN_WLR_OUT_NAME, 
                            num_mems);
    module_manager.add_port(mem_module, chain_wlr_port, ModuleManager::MODULE_OUTPUT_PORT);
  }

  /* Find the sram module in the module manager */
  ModuleId sram_mem_module = module_manager.find_module(circuit_lib.model_name(sram_model));

  /* Instanciate each submodule */
  for (size_t i = 0; i < num_mems; ++i) {
    size_t sram_mem_instance = module_manager.num_instance(mem_module, sram_mem_module);
    module_manager.add_child_module(mem_module, sram_mem_module);
    module_manager.add_configurable_child(mem_module, sram_mem_module, sram_mem_instance);

    /* Build module nets to wire wl outputs of sram modules to WL outputs of memory module */
    for (const auto& child_module_output_port : sram_wl_ports) {
      std::string chain_output_port_name = std::string(WL_SHIFT_REGISTER_CHAIN_WL_OUT_NAME); 
      add_module_output_nets_to_chain_mem_modules(module_manager, mem_module, 
                                                  chain_output_port_name, circuit_lib, 
                                                  child_module_output_port,
                                                  sram_mem_module, i, sram_mem_instance);
    }

    /* Build module nets to wire wlr outputs of sram modules to WLR outputs of memory module */
    for (const auto& child_module_output_port : sram_wlr_ports) {
      std::string chain_output_port_name = std::string(WL_SHIFT_REGISTER_CHAIN_WLR_OUT_NAME); 
      add_module_output_nets_to_chain_mem_modules(module_manager, mem_module, 
                                                  chain_output_port_name, circuit_lib, 
                                                  child_module_output_port,
                                                  sram_mem_module, i, sram_mem_instance);
    }
  }

  /* Build module nets to wire the configuration chain */
  add_module_nets_to_ql_memory_bank_shift_register_module(module_manager, mem_module,
                                                          circuit_lib, sram_input_ports[0], sram_output_ports[0],
                                                          std::string(WL_SHIFT_REGISTER_CHAIN_HEAD_NAME),
                                                          std::string(WL_SHIFT_REGISTER_CHAIN_TAIL_NAME));

  /* Add global ports to the pb_module:
   * This is a much easier job after adding sub modules (instances), 
   * we just need to find all the global ports from the child modules and build a list of it
   */
  add_module_global_ports_from_child_modules(module_manager, mem_module);

  return mem_module;
}

/*********************************************************************
 * This function to add nets for quicklogic memory banks
 * Each configuration region has independent memory bank circuitry
 * - Find the number of BLs and WLs required for each region
 * - Create BL and WL decoders, and add them to decoder library
 * - Create nets to connect from top-level module inputs to inputs of decoders
 * - Create nets to connect from outputs of decoders to BL/WL of configurable children
 *
 * @note this function only adds the BL configuration bus for decoders
 *
 * Detailed schematic of how memory banks are connected in the top-level:
 * Consider a random Region X, local BL address lines are aligned to the LSB of the
 * top-level BL address lines
 *
 *            top_bl_addr[N-1:0]
 *                 ^
 *                 | local_bl_addr[N-1:0]
 *                 |
 *           +-----+------------------+
 *           |     |                  |
 *           |  +-------------------+ |
 *           |  | Word Line Decoder | |
 *           |  +-------------------+ |
 *           |                        |
 *
 * The BL/WL decoders should have the same circuit designs no matter what region 
 * they are placed even when the number of configuration bits are different 
 * from one region to another!
 * This is designed to avoid any address collision between memory banks
 * since they are programmed in the same clock cycle
 * For example: 
 *   - Memory Bank A has 36 memory cells.
 *     Its BL decoder has 3 address bit and 6 data output bit
 *     Its WL decoder has 3 address bit and 6 data output bit
 *   - Memory Bank B has 16 memory cells.
 *     Its BL decoder has 2 address bit and 4 data output bit
 *     Its WL decoder has 2 address bit and 4 data output bit
 *   - If we try to program the 36th memory cell in bank A
 *     the BL address will be 3'b110
 *     the WL address will be 3'b110
 *     the data input will be 1'b0
 *   - If we try to program the 4th memory cell in bank A
 *     the BL address will be 3'b010
 *     the WL address will be 3'b010
 *     the data input will be 1'b1
 *     However, in both cases, this will trigger a parasitic programming in bank B
 *     the BL address will be 2'b10 
 *     the WL address will be 2'b10
 *     Assume the data input is expected to be 1'b1 for bank B
 *     but it will be overwritten to 1'b0 when programming the 36th cell in bank A!
 *
 * Detailed schematic of each memory bank:
 * @note The numbers are just made to show a simplified example, practical cases are more complicated!
 *
 *                            WL_enable  WL address 
 *                               |           |
 *                               v           v
 *                           +-----------------------------------------------+
 *                           |     Word Line Decoder                         |
 *                           +-----------------------------------------------+
 *               +---------+    |              |                    | 
 *  BL           |         |    |              |                    |
 *  enable  ---->|         |-----------+---------------+----  ...   |------+--> BL[0:2]
 *               |         |    |      |       |       |            |      |
 *               |         |    |      v       |       v            |      v
 *               | Bit     |    |   +-------+  |   +-------+        |  +------+
 *  BL           | Line    |    +-->| SRAM  |  +-->| SRAM  |        +->| SRAM |
 *  address ---->| Decoder |    |   | [0:8] |  |   | [0:5] |   ...  |  | [0:7]|
 *               |         |    |   +-------+  |   +-------+        |  +------+
 *               |         |    |              |                    |
 *               |         |-----------+--------------+---------    | -----+--> BL[0:9]
 *               |         |    |      |       |       |            |      |
 *               |         |    |      v       |       v            |      v
 *               |         |    |   +-------+  |   +-------+        |  +-------+
 *               |         |    +-->| SRAM  |  |   | SRAM  |        +->| SRAM  |
 *               |         |    |   | [0:80]|  |   | [0:63]|   ...  |  | [0:31]|
 *               |         |    |   +-------+  |   +-------+        |  +-------+
 *               |         |    |                                   |
 *               |         |    |     ...     ...    ...            |    ...
 *               |         |    |              |                    |
 *               |         |-----------+---------------+----  ---   | -----+--> BL[0:3]
 *               |         |    |      |       |       |            |      |
 *               |         |    |      v       |       v            |      v
 *               |         |    |   +-------+  |   +-------+        |  +-------+
 *               |         |    +-->| SRAM  |  +-->| SRAM  |        +->| SRAM  |
 *               |         |    |   |[0:5]  |  |   | [0:8] |   ...  |  | [0:2] |
 *               |         |    |   +-------+  |   +-------+        |  +-------+
 *  BL           |         |    v              v                    v
 *  data_in ---->|         |  WL[0:9]          WL[0:7]                WL[0:4]
 *               +---------+
 *
 **********************************************************************/
static 
void add_top_module_nets_cmos_ql_memory_bank_bl_decoder_config_bus(ModuleManager& module_manager,
                                                                   DecoderLibrary& decoder_lib,
                                                                   const ModuleId& top_module,
                                                                   const CircuitLibrary& circuit_lib,
                                                                   const CircuitModelId& sram_model,
                                                                   const TopModuleNumConfigBits& num_config_bits) {
  /* Find Enable port from the top-level module */ 
  ModulePortId en_port = module_manager.find_module_port(top_module, std::string(DECODER_ENABLE_PORT_NAME));
  BasicPort en_port_info = module_manager.module_port(top_module, en_port);

  /* Find data-in port from the top-level module */ 
  ModulePortId din_port = module_manager.find_module_port(top_module, std::string(DECODER_DATA_IN_PORT_NAME));
  BasicPort din_port_info = module_manager.module_port(top_module, din_port);

  /* Data in port should match the number of configuration regions */
  VTR_ASSERT(din_port_info.get_width() == module_manager.regions(top_module).size());

  /* Find BL and WL address port from the top-level module */ 
  ModulePortId bl_addr_port = module_manager.find_module_port(top_module, std::string(DECODER_BL_ADDRESS_PORT_NAME));
  BasicPort bl_addr_port_info = module_manager.module_port(top_module, bl_addr_port);

  /* Find the top-level number of BLs and WLs required to access each memory bit */
  size_t bl_addr_size = bl_addr_port_info.get_width();

  /* Each memory bank has a unified number of BL/WLs */
  size_t num_bls = 0;
  for (const auto& curr_config_bits : num_config_bits) {
     num_bls = std::max(num_bls, curr_config_bits.first);
  }

  /* Create separated memory bank circuitry, i.e., BL/WL decoders for each region */
  for (const ConfigRegionId& config_region : module_manager.regions(top_module)) {
    /************************************************************** 
     * Add the BL decoder module 
     * Search the decoder library
     * If we find one, we use the module.
     * Otherwise, we create one and add it to the decoder library
     */
    DecoderId bl_decoder_id = decoder_lib.find_decoder(bl_addr_size, num_bls,
                                                       true, true, false, false);
    if (DecoderId::INVALID() == bl_decoder_id) {
      bl_decoder_id = decoder_lib.add_decoder(bl_addr_size, num_bls, true, true, false, false);
    }
    VTR_ASSERT(DecoderId::INVALID() != bl_decoder_id);

    /* Create a module if not existed yet */
    std::string bl_decoder_module_name = generate_memory_decoder_with_data_in_subckt_name(bl_addr_size, num_bls);
    ModuleId bl_decoder_module = module_manager.find_module(bl_decoder_module_name);
    if (ModuleId::INVALID() == bl_decoder_module) {
      /* BL decoder has the same ports as the frame-based decoders
       * We reuse it here
       */
      bl_decoder_module = build_bl_memory_decoder_module(module_manager,
                                                         decoder_lib,
                                                         bl_decoder_id);
    }
    VTR_ASSERT(ModuleId::INVALID() != bl_decoder_module);
    size_t curr_bl_decoder_instance_id = module_manager.num_instance(top_module, bl_decoder_module);
    module_manager.add_child_module(top_module, bl_decoder_module);

    /************************************************************** 
     * Add module nets from the top module to BL decoder's inputs
     */
    ModulePortId bl_decoder_en_port = module_manager.find_module_port(bl_decoder_module, std::string(DECODER_ENABLE_PORT_NAME));
    BasicPort bl_decoder_en_port_info = module_manager.module_port(bl_decoder_module, bl_decoder_en_port);

    ModulePortId bl_decoder_addr_port = module_manager.find_module_port(bl_decoder_module, std::string(DECODER_ADDRESS_PORT_NAME));
    BasicPort bl_decoder_addr_port_info = module_manager.module_port(bl_decoder_module, bl_decoder_addr_port);

    ModulePortId bl_decoder_din_port = module_manager.find_module_port(bl_decoder_module, std::string(DECODER_DATA_IN_PORT_NAME));
    BasicPort bl_decoder_din_port_info = module_manager.module_port(bl_decoder_module, bl_decoder_din_port);

    /* Data in port of the local BL decoder should always be 1 */
    VTR_ASSERT(1 == bl_decoder_din_port_info.get_width());

    /* Top module Enable port -> BL Decoder Enable port */
    add_module_bus_nets(module_manager,
                        top_module,
                        top_module, 0, en_port,
                        bl_decoder_module, curr_bl_decoder_instance_id, bl_decoder_en_port);

    /* Top module Address port -> BL Decoder Address port */
    add_module_bus_nets(module_manager,
                        top_module,
                        top_module, 0, bl_addr_port,
                        bl_decoder_module, curr_bl_decoder_instance_id, bl_decoder_addr_port);

    /* Top module data_in port -> BL Decoder data_in port:
     * Note that each region has independent data_in connection from the top-level module 
     * The pin index is the configuration region index
     */
    ModuleNetId din_net = create_module_source_pin_net(module_manager, top_module, 
                                                       top_module, 0, 
                                                       din_port,
                                                       din_port_info.pins()[size_t(config_region)]);
    VTR_ASSERT(ModuleNetId::INVALID() != din_net);

    /* Configure the net sink */
    module_manager.add_module_net_sink(top_module, din_net, bl_decoder_module, curr_bl_decoder_instance_id, bl_decoder_din_port, bl_decoder_din_port_info.pins()[0]);

    /************************************************************** 
     * Precompute the BLs and WLs distribution across the FPGA fabric
     * The distribution is a matrix which contains the starting index of BL/WL for each column or row
     */
    std::pair<int, int> child_x_range = compute_memory_bank_regional_configurable_child_x_range(module_manager, top_module, config_region);
    std::map<int, size_t> num_bls_per_tile = compute_memory_bank_regional_bitline_numbers_per_tile(module_manager, top_module,
                                                                                                   config_region,
                                                                                                   circuit_lib, sram_model);
    std::map<int, size_t> bl_start_index_per_tile = compute_memory_bank_regional_blwl_start_index_per_tile(child_x_range, num_bls_per_tile);

    /************************************************************** 
     * Add nets from BL data out to each configurable child
     * BL data output pins are connected to the BL input pins of each PB/CB/SB
     * For all the PB/CB/SB in the same column, they share the same set of BLs
     * A quick example
     *
     *   BL[i .. i + sqrt(N)]     
     *     |
     *     |    CLB[1][H]
     *     |   +---------+     
     *     |   |  SRAM   |     
     *     +-->|  [0..N] |     
     *     |   +---------+
     *     |
     *    ...
     *     |    CLB[1][1]
     *     |   +---------+     
     *     |   |  SRAM   |     
     *     +-->|  [0..N] |     
     *     |   +---------+
     *     |
     */
    ModulePortId bl_decoder_dout_port = module_manager.find_module_port(bl_decoder_module, std::string(DECODER_DATA_OUT_PORT_NAME));
    BasicPort bl_decoder_dout_port_info = module_manager.module_port(bl_decoder_module, bl_decoder_dout_port);

    for (size_t child_id = 0; child_id < module_manager.region_configurable_children(top_module, config_region).size(); ++child_id) {
      ModuleId child_module = module_manager.region_configurable_children(top_module, config_region)[child_id];
      vtr::Point<int> coord = module_manager.region_configurable_child_coordinates(top_module, config_region)[child_id]; 

      size_t child_instance = module_manager.region_configurable_child_instances(top_module, config_region)[child_id];

      /* Find the BL port */
      ModulePortId child_bl_port = module_manager.find_module_port(child_module, std::string(MEMORY_BL_PORT_NAME));
      BasicPort child_bl_port_info = module_manager.module_port(child_module, child_bl_port);

      size_t cur_bl_index = 0;

      for (const size_t& sink_bl_pin : child_bl_port_info.pins()) {
        size_t bl_pin_id = bl_start_index_per_tile[coord.x()] + cur_bl_index;
        /* Find the BL decoder data index: 
         * It should be the starting index plus an offset which is the residual when divided by the number of BLs in this tile
         */
        VTR_ASSERT(bl_pin_id < bl_decoder_dout_port_info.pins().size());

        /* Create net */
        ModuleNetId net = create_module_source_pin_net(module_manager, top_module,
                                                       bl_decoder_module, curr_bl_decoder_instance_id,
                                                       bl_decoder_dout_port,
                                                       bl_decoder_dout_port_info.pins()[bl_pin_id]);
        VTR_ASSERT(ModuleNetId::INVALID() != net);

        /* Add net sink */
        module_manager.add_module_net_sink(top_module, net,
                                           child_module, child_instance, child_bl_port, sink_bl_pin);

        cur_bl_index++;
      }
    }

    /************************************************************** 
     * Add the BL and WL decoders to the end of configurable children list
     * Note: this MUST be done after adding all the module nets to other regular configurable children
     */
    module_manager.add_configurable_child(top_module, bl_decoder_module, curr_bl_decoder_instance_id);
    module_manager.add_configurable_child_to_region(top_module,
                                                    config_region,
                                                    bl_decoder_module, 
                                                    curr_bl_decoder_instance_id,
                                                    module_manager.configurable_children(top_module).size() - 1);
  }
}

/*********************************************************************
 * Top-level function to add nets for quicklogic memory banks
 * Each configuration region has independent memory bank circuitry
 * - Find the number of BLs and WLs required for each region
 * - Create BL and WL decoders, and add them to decoder library
 * - Create nets to connect from top-level module inputs to inputs of decoders
 * - Create nets to connect from outputs of decoders to BL/WL of configurable children
 *
 * @note this function only adds the WL configuration bus for decoders
 *
 * @note see detailed explanation on the bus connection in function add_top_module_nets_cmos_ql_memory_bank_bl_decoder_config_bus()
 **********************************************************************/
static 
void add_top_module_nets_cmos_ql_memory_bank_wl_decoder_config_bus(ModuleManager& module_manager,
                                                                   DecoderLibrary& decoder_lib,
                                                                   const ModuleId& top_module,
                                                                   const CircuitLibrary& circuit_lib,
                                                                   const CircuitModelId& sram_model,
                                                                   const TopModuleNumConfigBits& num_config_bits) {
  /* Find Enable port from the top-level module */ 
  ModulePortId en_port = module_manager.find_module_port(top_module, std::string(DECODER_ENABLE_PORT_NAME));
  BasicPort en_port_info = module_manager.module_port(top_module, en_port);
  
  /* Find readback port from the top-level module */ 
  ModulePortId readback_port = module_manager.find_module_port(top_module, std::string(DECODER_READBACK_PORT_NAME));
  BasicPort readback_port_info;

  /* Readback port if available, should be a 1-bit port */
  if (readback_port) {
    readback_port_info = module_manager.module_port(top_module, readback_port);
    VTR_ASSERT(readback_port_info.get_width() == 1);
  }

  /* Find BL and WL address port from the top-level module */ 
  ModulePortId wl_addr_port = module_manager.find_module_port(top_module, std::string(DECODER_WL_ADDRESS_PORT_NAME));
  BasicPort wl_addr_port_info = module_manager.module_port(top_module, wl_addr_port);

  /* Find the top-level number of BLs and WLs required to access each memory bit */
  size_t wl_addr_size = wl_addr_port_info.get_width();

  /* Each memory bank has a unified number of BL/WLs */
  size_t num_wls = 0;
  for (const auto& curr_config_bits : num_config_bits) {
     num_wls = std::max(num_wls, curr_config_bits.second);
  }

  /* Create separated memory bank circuitry, i.e., BL/WL decoders for each region */
  for (const ConfigRegionId& config_region : module_manager.regions(top_module)) {
    /************************************************************** 
     * Add the WL decoder module 
     * Search the decoder library
     * If we find one, we use the module.
     * Otherwise, we create one and add it to the decoder library
     */
    DecoderId wl_decoder_id = decoder_lib.find_decoder(wl_addr_size, num_wls,
                                                       true, false, false, readback_port != ModulePortId::INVALID());
    if (DecoderId::INVALID() == wl_decoder_id) {
      wl_decoder_id = decoder_lib.add_decoder(wl_addr_size, num_wls, true, false, false, readback_port != ModulePortId::INVALID());
    }
    VTR_ASSERT(DecoderId::INVALID() != wl_decoder_id);

    /* Create a module if not existed yet */
    std::string wl_decoder_module_name = generate_memory_decoder_subckt_name(wl_addr_size, num_wls);
    ModuleId wl_decoder_module = module_manager.find_module(wl_decoder_module_name);
    if (ModuleId::INVALID() == wl_decoder_module) {
      /* BL decoder has the same ports as the frame-based decoders
       * We reuse it here
       */
      wl_decoder_module = build_wl_memory_decoder_module(module_manager,
                                                         decoder_lib,
                                                         wl_decoder_id);
    }
    VTR_ASSERT(ModuleId::INVALID() != wl_decoder_module);
    size_t curr_wl_decoder_instance_id = module_manager.num_instance(top_module, wl_decoder_module);
    module_manager.add_child_module(top_module, wl_decoder_module);

    /************************************************************** 
     * Add module nets from the top module to WL decoder's inputs 
     */
    ModulePortId wl_decoder_en_port = module_manager.find_module_port(wl_decoder_module, std::string(DECODER_ENABLE_PORT_NAME));
    BasicPort wl_decoder_en_port_info = module_manager.module_port(wl_decoder_module, wl_decoder_en_port);

    ModulePortId wl_decoder_addr_port = module_manager.find_module_port(wl_decoder_module, std::string(DECODER_ADDRESS_PORT_NAME));
    BasicPort wl_decoder_addr_port_info = module_manager.module_port(wl_decoder_module, wl_decoder_addr_port);

    ModulePortId wl_decoder_readback_port = module_manager.find_module_port(wl_decoder_module, std::string(DECODER_READBACK_PORT_NAME));
    BasicPort wl_decoder_readback_port_info;
    if (wl_decoder_readback_port) { 
      wl_decoder_readback_port_info = module_manager.module_port(wl_decoder_module, wl_decoder_readback_port);
    }

    /* Top module Enable port -> WL Decoder Enable port */
    add_module_bus_nets(module_manager,
                        top_module,
                        top_module, 0, en_port,
                        wl_decoder_module, curr_wl_decoder_instance_id, wl_decoder_en_port);

    /* Top module Address port -> WL Decoder Address port */
    add_module_bus_nets(module_manager,
                        top_module,
                        top_module, 0, wl_addr_port,
                        wl_decoder_module, curr_wl_decoder_instance_id, wl_decoder_addr_port);

    /* Top module readback port -> WL Decoder readback port */
    if (wl_decoder_readback_port) { 
      add_module_bus_nets(module_manager,
                          top_module,
                          top_module, 0, readback_port,
                          wl_decoder_module, curr_wl_decoder_instance_id, wl_decoder_readback_port);
    }

    /************************************************************** 
     * Precompute the BLs and WLs distribution across the FPGA fabric
     * The distribution is a matrix which contains the starting index of BL/WL for each column or row
     */
    std::pair<int, int> child_y_range = compute_memory_bank_regional_configurable_child_y_range(module_manager, top_module, config_region);
    std::map<int, size_t> num_wls_per_tile = compute_memory_bank_regional_wordline_numbers_per_tile(module_manager, top_module,
                                                                                                    config_region,
                                                                                                    circuit_lib, sram_model);
    std::map<int, size_t> wl_start_index_per_tile = compute_memory_bank_regional_blwl_start_index_per_tile(child_y_range, num_wls_per_tile);

    /************************************************************** 
     * Add nets from WL data out to each configurable child
     */
    ModulePortId wl_decoder_dout_port = module_manager.find_module_port(wl_decoder_module, std::string(DECODER_DATA_OUT_PORT_NAME));
    BasicPort wl_decoder_dout_port_info = module_manager.module_port(wl_decoder_module, wl_decoder_dout_port);

    /* Note we skip the last child which is the bl decoder added */
    for (size_t child_id = 0; child_id < module_manager.region_configurable_children(top_module, config_region).size(); ++child_id) {
      ModuleId child_module = module_manager.region_configurable_children(top_module, config_region)[child_id];
      vtr::Point<int> coord = module_manager.region_configurable_child_coordinates(top_module, config_region)[child_id]; 

      size_t child_instance = module_manager.region_configurable_child_instances(top_module, config_region)[child_id];

      /* Find the WL port. If the child does not have WL port, bypass it. It is usually a decoder module */
      ModulePortId child_wl_port = module_manager.find_module_port(child_module, std::string(MEMORY_WL_PORT_NAME));
      if (!child_wl_port) {
        continue;
      }
      BasicPort child_wl_port_info = module_manager.module_port(child_module, child_wl_port);

      size_t cur_wl_index = 0;

      for (const size_t& sink_wl_pin : child_wl_port_info.pins()) {
        size_t wl_pin_id = wl_start_index_per_tile[coord.y()] + cur_wl_index;
        VTR_ASSERT(wl_pin_id < wl_decoder_dout_port_info.pins().size());

        /* Create net */
        ModuleNetId net = create_module_source_pin_net(module_manager, top_module,
                                                       wl_decoder_module, curr_wl_decoder_instance_id,
                                                       wl_decoder_dout_port,
                                                       wl_decoder_dout_port_info.pins()[wl_pin_id]);
        VTR_ASSERT(ModuleNetId::INVALID() != net);

        /* Add net sink */
        module_manager.add_module_net_sink(top_module, net,
                                           child_module, child_instance, child_wl_port, sink_wl_pin);
      
        cur_wl_index++;
      }
    }

    /************************************************************** 
     * Optional: Add nets from WLR data out to each configurable child
     */
    ModulePortId wl_decoder_data_ren_port = module_manager.find_module_port(wl_decoder_module, std::string(DECODER_DATA_READ_ENABLE_PORT_NAME));
    BasicPort wl_decoder_data_ren_port_info;
    if (wl_decoder_data_ren_port) { 
      wl_decoder_data_ren_port_info = module_manager.module_port(wl_decoder_module, wl_decoder_data_ren_port);
      for (size_t child_id = 0; child_id < module_manager.region_configurable_children(top_module, config_region).size(); ++child_id) {
        ModuleId child_module = module_manager.region_configurable_children(top_module, config_region)[child_id];
        vtr::Point<int> coord = module_manager.region_configurable_child_coordinates(top_module, config_region)[child_id]; 
  
        size_t child_instance = module_manager.region_configurable_child_instances(top_module, config_region)[child_id];
  
        /* Find the WLR port. If the child does not have WLR port, bypass it. It is usually a decoder module */
        ModulePortId child_wlr_port = module_manager.find_module_port(child_module, std::string(MEMORY_WLR_PORT_NAME));
        if (!child_wlr_port) {
          continue;
        }
        BasicPort child_wlr_port_info = module_manager.module_port(child_module, child_wlr_port);
  
        size_t cur_wlr_index = 0;
  
        for (const size_t& sink_wlr_pin : child_wlr_port_info.pins()) {
          size_t wlr_pin_id = wl_start_index_per_tile[coord.y()] + cur_wlr_index;
          VTR_ASSERT(wlr_pin_id < wl_decoder_data_ren_port_info.pins().size());
  
          /* Create net */
          ModuleNetId net = create_module_source_pin_net(module_manager, top_module,
                                                         wl_decoder_module, curr_wl_decoder_instance_id,
                                                         wl_decoder_data_ren_port,
                                                         wl_decoder_data_ren_port_info.pins()[wlr_pin_id]);
          VTR_ASSERT(ModuleNetId::INVALID() != net);
  
          /* Add net sink */
          module_manager.add_module_net_sink(top_module, net,
                                             child_module, child_instance, child_wlr_port, sink_wlr_pin);
        
          cur_wlr_index++;
        }
      }
    }

    /************************************************************** 
     * Add the BL and WL decoders to the end of configurable children list
     * Note: this MUST be done after adding all the module nets to other regular configurable children
     */
    module_manager.add_configurable_child(top_module, wl_decoder_module, curr_wl_decoder_instance_id);
    module_manager.add_configurable_child_to_region(top_module,
                                                    config_region,
                                                    wl_decoder_module, 
                                                    curr_wl_decoder_instance_id,
                                                    module_manager.configurable_children(top_module).size() - 1);
  }
}

/*********************************************************************
 * This function to add nets for quicklogic memory banks using flatten BLs and WLs
 * Each configuration region has independent BL/WLs
 * - Find the number of BLs and WLs required for each region
 * - Create nets to connect from top-level module inputs to BL/WL of configurable children
 *
 * @note this function only adds the BL configuration bus
 *
 * Detailed schematic of each memory bank:
 * @note The numbers are just made to show a simplified example, practical cases are more complicated!
 *
 *                 BL[0:9]        BL[10:17]          BL[18:22]
 *                   |              |                    | 
 *                   |              |                    |
 *     WL[0:3]-->-----------+---------------+----  ...   |------+-->        
 *                   |      |       |       |            |      |
 *                   |      v       |       v            |      v
 *                   |   +-------+  |   +-------+        |  +------+
 *                   +-->| SRAM  |  +-->| SRAM  |        +->| SRAM |
 *                   |   | [0:8] |  |   | [0:5] |   ...  |  | [0:7]|
 *                   |   +-------+  |   +-------+        |  +------+
 *                   |              |                    |
 *     WL[4:14]  -----------+--------------+---------    | -----+-->        
 *                   |      |       |       |            |      |
 *                   |      v       |       v            |      v
 *                   |   +-------+  |   +-------+        |  +-------+
 *                   +-->| SRAM  |  |   | SRAM  |        +->| SRAM  |
 *                   |   | [0:80]|  |   | [0:63]|   ...  |  | [0:31]|
 *                   |   +-------+  |   +-------+        |  +-------+
 *                   |                                   |
 *                   |     ...     ...    ...            |    ...
 *                   |              |                    |
 *     WL[15:18] -----------+---------------+----  ---   | -----+-->        
 *                   |      |       |       |            |      |
 *                   |      v       |       v            |      v
 *                   |   +-------+  |   +-------+        |  +-------+
 *                   +-->| SRAM  |  +-->| SRAM  |        +->| SRAM  |
 *                   |   |[0:5]  |  |   | [0:8] |   ...  |  | [0:2] |
 *                   |   +-------+  |   +-------+        |  +-------+
 *                   v              v                    v
 *                 WL[0:9]          WL[0:7]                WL[0:4]
 *               
 **********************************************************************/
static 
void add_top_module_nets_cmos_ql_memory_bank_bl_flatten_config_bus(ModuleManager& module_manager,
                                                                   const ModuleId& top_module,
                                                                   const CircuitLibrary& circuit_lib,
                                                                   const CircuitModelId& sram_model) {
  /* Create connections between BLs of top-level module and BLs of child modules for each region */
  for (const ConfigRegionId& config_region : module_manager.regions(top_module)) {
    /************************************************************** 
     * Precompute the BLs and WLs distribution across the FPGA fabric
     * The distribution is a matrix which contains the starting index of BL/WL for each column or row
     */
    std::pair<int, int> child_x_range = compute_memory_bank_regional_configurable_child_x_range(module_manager, top_module, config_region);
    std::map<int, size_t> num_bls_per_tile = compute_memory_bank_regional_bitline_numbers_per_tile(module_manager, top_module,
                                                                                                   config_region,
                                                                                                   circuit_lib, sram_model);
    std::map<int, size_t> bl_start_index_per_tile = compute_memory_bank_regional_blwl_start_index_per_tile(child_x_range, num_bls_per_tile);

    /************************************************************** 
     * Add BL nets from top module to each configurable child
     * BL pins of top module are connected to the BL input pins of each PB/CB/SB
     * For all the PB/CB/SB in the same column, they share the same set of BLs
     * A quick example
     *
     *   BL[i .. i + sqrt(N)]     
     *     |
     *     |    CLB[1][H]
     *     |   +---------+     
     *     |   |  SRAM   |     
     *     +-->|  [0..N] |     
     *     |   +---------+
     *     |
     *    ...
     *     |    CLB[1][1]
     *     |   +---------+     
     *     |   |  SRAM   |     
     *     +-->|  [0..N] |     
     *     |   +---------+
     *     |
     */
    ModulePortId top_module_bl_port = module_manager.find_module_port(top_module, generate_regional_blwl_port_name(std::string(MEMORY_BL_PORT_NAME), config_region));
    BasicPort top_module_bl_port_info = module_manager.module_port(top_module, top_module_bl_port);

    for (size_t child_id = 0; child_id < module_manager.region_configurable_children(top_module, config_region).size(); ++child_id) {
      ModuleId child_module = module_manager.region_configurable_children(top_module, config_region)[child_id];
      vtr::Point<int> coord = module_manager.region_configurable_child_coordinates(top_module, config_region)[child_id]; 

      size_t child_instance = module_manager.region_configurable_child_instances(top_module, config_region)[child_id];

      /* Find the BL port */
      ModulePortId child_bl_port = module_manager.find_module_port(child_module, std::string(MEMORY_BL_PORT_NAME));
      BasicPort child_bl_port_info = module_manager.module_port(child_module, child_bl_port);

      size_t cur_bl_index = 0;

      for (const size_t& sink_bl_pin : child_bl_port_info.pins()) {
        size_t bl_pin_id = bl_start_index_per_tile[coord.x()] + cur_bl_index;
        VTR_ASSERT(bl_pin_id < top_module_bl_port_info.pins().size());

        /* Create net */
        ModuleNetId net = create_module_source_pin_net(module_manager, top_module,
                                                       top_module, 0,
                                                       top_module_bl_port,
                                                       top_module_bl_port_info.pins()[bl_pin_id]);
        VTR_ASSERT(ModuleNetId::INVALID() != net);

        /* Add net sink */
        module_manager.add_module_net_sink(top_module, net,
                                           child_module, child_instance, child_bl_port, sink_bl_pin);

        cur_bl_index++;
      }
    }
  }
}

/*********************************************************************
 * Top-level function to add nets for quicklogic memory banks using flatten BL/WLs
 * Each configuration region has independent BL/WLs
 * - Find the number of BLs and WLs required for each region
 * - Create nets to connect from top-level module inputs to BL/WL of configurable children
 *
 * @note this function only adds the WL configuration bus
 *
 * @note see detailed explanation on the bus connection in function add_top_module_nets_cmos_ql_memory_bank_bl_flatten_config_bus()
 **********************************************************************/
static 
void add_top_module_nets_cmos_ql_memory_bank_wl_flatten_config_bus(ModuleManager& module_manager,
                                                                   const ModuleId& top_module,
                                                                   const CircuitLibrary& circuit_lib,
                                                                   const CircuitModelId& sram_model) {
  /* Create connections between WLs of top-level module and WLs of child modules for each region */
  for (const ConfigRegionId& config_region : module_manager.regions(top_module)) {
    /************************************************************** 
     * Precompute the BLs and WLs distribution across the FPGA fabric
     * The distribution is a matrix which contains the starting index of BL/WL for each column or row
     */
    std::pair<int, int> child_y_range = compute_memory_bank_regional_configurable_child_y_range(module_manager, top_module, config_region);
    std::map<int, size_t> num_wls_per_tile = compute_memory_bank_regional_wordline_numbers_per_tile(module_manager, top_module,
                                                                                                    config_region,
                                                                                                    circuit_lib, sram_model);
    std::map<int, size_t> wl_start_index_per_tile = compute_memory_bank_regional_blwl_start_index_per_tile(child_y_range, num_wls_per_tile);

    /************************************************************** 
     * Add WL nets from top module to each configurable child
     */
    ModulePortId top_module_wl_port = module_manager.find_module_port(top_module, generate_regional_blwl_port_name(std::string(MEMORY_WL_PORT_NAME), config_region));
    BasicPort top_module_wl_port_info = module_manager.module_port(top_module, top_module_wl_port);

    for (size_t child_id = 0; child_id < module_manager.region_configurable_children(top_module, config_region).size(); ++child_id) {
      ModuleId child_module = module_manager.region_configurable_children(top_module, config_region)[child_id];
      vtr::Point<int> coord = module_manager.region_configurable_child_coordinates(top_module, config_region)[child_id]; 

      size_t child_instance = module_manager.region_configurable_child_instances(top_module, config_region)[child_id];

      /* Find the WL port */
      ModulePortId child_wl_port = module_manager.find_module_port(child_module, std::string(MEMORY_WL_PORT_NAME));
      BasicPort child_wl_port_info = module_manager.module_port(child_module, child_wl_port);

      size_t cur_wl_index = 0;

      for (const size_t& sink_wl_pin : child_wl_port_info.pins()) {
        size_t wl_pin_id = wl_start_index_per_tile[coord.y()] + cur_wl_index;
        VTR_ASSERT(wl_pin_id < top_module_wl_port_info.pins().size());

        /* Create net */
        ModuleNetId net = create_module_source_pin_net(module_manager, top_module,
                                                       top_module, 0,
                                                       top_module_wl_port,
                                                       top_module_wl_port_info.pins()[wl_pin_id]);
        VTR_ASSERT(ModuleNetId::INVALID() != net);

        /* Add net sink */
        module_manager.add_module_net_sink(top_module, net,
                                           child_module, child_instance, child_wl_port, sink_wl_pin);
      
        cur_wl_index++;
      }
    }

    /************************************************************** 
     * Optional: Add WLR nets from top module to each configurable child
     */
    ModulePortId top_module_wlr_port = module_manager.find_module_port(top_module, generate_regional_blwl_port_name(std::string(MEMORY_WLR_PORT_NAME), config_region));
    BasicPort top_module_wlr_port_info;
    if (top_module_wlr_port) { 
      top_module_wlr_port_info = module_manager.module_port(top_module, top_module_wlr_port);
      for (size_t child_id = 0; child_id < module_manager.region_configurable_children(top_module, config_region).size(); ++child_id) {
        ModuleId child_module = module_manager.region_configurable_children(top_module, config_region)[child_id];
        vtr::Point<int> coord = module_manager.region_configurable_child_coordinates(top_module, config_region)[child_id]; 
  
        size_t child_instance = module_manager.region_configurable_child_instances(top_module, config_region)[child_id];
  
        /* Find the WL port */
        ModulePortId child_wlr_port = module_manager.find_module_port(child_module, std::string(MEMORY_WLR_PORT_NAME));
        BasicPort child_wlr_port_info = module_manager.module_port(child_module, child_wlr_port);
  
        size_t cur_wlr_index = 0;
  
        for (const size_t& sink_wlr_pin : child_wlr_port_info.pins()) {
          size_t wlr_pin_id = wl_start_index_per_tile[coord.y()] + cur_wlr_index;
          VTR_ASSERT(wlr_pin_id < top_module_wlr_port_info.pins().size());
  
          /* Create net */
          ModuleNetId net = create_module_source_pin_net(module_manager, top_module,
                                                         top_module, 0,
                                                         top_module_wlr_port,
                                                         top_module_wlr_port_info.pins()[wlr_pin_id]);
          VTR_ASSERT(ModuleNetId::INVALID() != net);
  
          /* Add net sink */
          module_manager.add_module_net_sink(top_module, net,
                                             child_module, child_instance, child_wlr_port, sink_wlr_pin);
        
          cur_wlr_index++;
        }
      }
    }
  }
}

/*********************************************************************
 * This function to add nets for QuickLogic memory bank
 * We build the net connects between the head ports of BL shift register banks 
 * and the head ports of top-level module
 **********************************************************************/
static 
void add_top_module_nets_cmos_ql_memory_bank_bl_shift_register_bank_heads(ModuleManager& module_manager,
                                                                          const ModuleId& top_module,
                                                                          const MemoryBankShiftRegisterBanks& sr_banks) {
  std::string head_port_name(BL_SHIFT_REGISTER_CHAIN_HEAD_NAME);

  for (const ConfigRegionId& config_region : module_manager.regions(top_module)) {
    ModulePortId sr_head_port = module_manager.find_module_port(top_module, generate_regional_blwl_port_name(head_port_name, config_region));
    BasicPort sr_head_port_info = module_manager.module_port(top_module, sr_head_port);

    /* Iterate over each shift register banks */
    for (const auto& bank : sr_banks.bl_banks(config_region)) {
      /* Get the module and instance ids */
      ModuleId sr_bank_module = sr_banks.bl_shift_register_bank_module(config_region, bank);
      size_t sr_bank_instance = sr_banks.bl_shift_register_bank_instance(config_region, bank);

      VTR_ASSERT(sr_bank_module);

      ModulePortId sr_module_head_port = module_manager.find_module_port(sr_bank_module, head_port_name);
      BasicPort sr_module_head_port_info = module_manager.module_port(sr_bank_module, sr_module_head_port);
      VTR_ASSERT(sr_module_head_port_info.get_width() == 1);
      for (size_t ipin = 0; ipin < sr_module_head_port_info.pins().size(); ++ipin) {
        /* Create net */
        ModuleNetId net = create_module_source_pin_net(module_manager, top_module,
                                                       top_module, 0,
                                                       sr_head_port,
                                                       sr_head_port_info.pins()[size_t(bank)]);
        VTR_ASSERT(ModuleNetId::INVALID() != net);

        /* Add net sink */
        module_manager.add_module_net_sink(top_module, net,
                                           sr_bank_module, sr_bank_instance, sr_module_head_port, sr_module_head_port_info.pins()[ipin]);
      }
    }
  }
}

/*********************************************************************
 * This function to add nets for QuickLogic memory bank
 * We build the net connects between the head ports of BL shift register banks 
 * and the head ports of top-level module
 **********************************************************************/
static 
void add_top_module_nets_cmos_ql_memory_bank_wl_shift_register_bank_heads(ModuleManager& module_manager,
                                                                          const ModuleId& top_module,
                                                                          const MemoryBankShiftRegisterBanks& sr_banks) {
  std::string head_port_name(WL_SHIFT_REGISTER_CHAIN_HEAD_NAME);

  for (const ConfigRegionId& config_region : module_manager.regions(top_module)) {
    ModulePortId sr_head_port = module_manager.find_module_port(top_module, generate_regional_blwl_port_name(head_port_name, config_region));
    BasicPort sr_head_port_info = module_manager.module_port(top_module, sr_head_port);

    /* Iterate over each shift register banks */
    for (const auto& bank : sr_banks.wl_banks(config_region)) {
      /* Get the module and instance ids */
      ModuleId sr_bank_module = sr_banks.wl_shift_register_bank_module(config_region, bank);
      size_t sr_bank_instance = sr_banks.wl_shift_register_bank_instance(config_region, bank);

      VTR_ASSERT(sr_bank_module);

      ModulePortId sr_module_head_port = module_manager.find_module_port(sr_bank_module, head_port_name);
      BasicPort sr_module_head_port_info = module_manager.module_port(sr_bank_module, sr_module_head_port);
      VTR_ASSERT(sr_module_head_port_info.get_width() == 1);
      for (size_t ipin = 0; ipin < sr_module_head_port_info.pins().size(); ++ipin) {
        /* Create net */
        ModuleNetId net = create_module_source_pin_net(module_manager, top_module,
                                                       top_module, 0,
                                                       sr_head_port,
                                                       sr_head_port_info.pins()[size_t(bank)]);
        VTR_ASSERT(ModuleNetId::INVALID() != net);

        /* Add net sink */
        module_manager.add_module_net_sink(top_module, net,
                                           sr_bank_module, sr_bank_instance, sr_module_head_port, sr_module_head_port_info.pins()[ipin]);
      }
    }
  }
}

/*********************************************************************
 * This function to add nets for QuickLogic memory bank
 * We build the net connects between the tail ports of BL shift register banks 
 * and the tail ports of top-level module
 **********************************************************************/
static 
void add_top_module_nets_cmos_ql_memory_bank_bl_shift_register_bank_tails(ModuleManager& module_manager,
                                                                          const ModuleId& top_module,
                                                                          const MemoryBankShiftRegisterBanks& sr_banks) {
  std::string tail_port_name(BL_SHIFT_REGISTER_CHAIN_TAIL_NAME);
  for (const ConfigRegionId& config_region : module_manager.regions(top_module)) {
    ModulePortId sr_tail_port = module_manager.find_module_port(top_module, generate_regional_blwl_port_name(tail_port_name, config_region));
    BasicPort sr_tail_port_info = module_manager.module_port(top_module, sr_tail_port);

    /* Iterate over each shift register banks */
    for (const auto& bank : sr_banks.bl_banks(config_region)) {
      /* Get the module and instance ids */
      ModuleId sr_bank_module = sr_banks.bl_shift_register_bank_module(config_region, bank);
      size_t sr_bank_instance = sr_banks.bl_shift_register_bank_instance(config_region, bank);
      VTR_ASSERT(sr_bank_module);

      ModulePortId sr_module_tail_port = module_manager.find_module_port(sr_bank_module, tail_port_name);
      BasicPort sr_module_tail_port_info = module_manager.module_port(sr_bank_module, sr_module_tail_port);
      VTR_ASSERT(sr_module_tail_port_info.get_width() == 1);
      for (size_t ipin = 0; ipin < sr_module_tail_port_info.pins().size(); ++ipin) {
        /* Create net */
        ModuleNetId net = create_module_source_pin_net(module_manager, top_module,
                                                       sr_bank_module, sr_bank_instance,
                                                       sr_module_tail_port, sr_module_tail_port_info.pins()[ipin]);
        VTR_ASSERT(ModuleNetId::INVALID() != net);

        /* Add net sink */
        module_manager.add_module_net_sink(top_module, net,
                                           top_module, 0,
                                           sr_tail_port, sr_tail_port_info.pins()[size_t(bank)]);
      }
    }
  }
}

/*********************************************************************
 * This function to add nets for QuickLogic memory bank
 * We build the net connects between the tail ports of WL shift register banks 
 * and the tail ports of top-level module
 **********************************************************************/
static 
void add_top_module_nets_cmos_ql_memory_bank_wl_shift_register_bank_tails(ModuleManager& module_manager,
                                                                          const ModuleId& top_module,
                                                                          const MemoryBankShiftRegisterBanks& sr_banks) {
  std::string tail_port_name(WL_SHIFT_REGISTER_CHAIN_TAIL_NAME);
  for (const ConfigRegionId& config_region : module_manager.regions(top_module)) {
    ModulePortId sr_tail_port = module_manager.find_module_port(top_module, generate_regional_blwl_port_name(tail_port_name, config_region));
    BasicPort sr_tail_port_info = module_manager.module_port(top_module, sr_tail_port);

    /* Iterate over each shift register banks */
    for (const auto& bank : sr_banks.wl_banks(config_region)) {
      /* Get the module and instance ids */
      ModuleId sr_bank_module = sr_banks.wl_shift_register_bank_module(config_region, bank);
      size_t sr_bank_instance = sr_banks.wl_shift_register_bank_instance(config_region, bank);
      VTR_ASSERT(sr_bank_module);

      ModulePortId sr_module_tail_port = module_manager.find_module_port(sr_bank_module, tail_port_name);
      BasicPort sr_module_tail_port_info = module_manager.module_port(sr_bank_module, sr_module_tail_port);
      VTR_ASSERT(sr_module_tail_port_info.get_width() == 1);
      for (size_t ipin = 0; ipin < sr_module_tail_port_info.pins().size(); ++ipin) {
        /* Create net */
        ModuleNetId net = create_module_source_pin_net(module_manager, top_module,
                                                       sr_bank_module, sr_bank_instance,
                                                       sr_module_tail_port, sr_module_tail_port_info.pins()[ipin]);
        VTR_ASSERT(ModuleNetId::INVALID() != net);

        /* Add net sink */
        module_manager.add_module_net_sink(top_module, net,
                                           top_module, 0,
                                           sr_tail_port, sr_tail_port_info.pins()[size_t(bank)]);
      }
    }
  }
}

/************************************************************** 
 * Add BL/WL nets from shift register module to each configurable child
 * BL pins of shift register module are connected to the BL input pins of each PB/CB/SB
 * For all the PB/CB/SB in the same column, they share the same set of BLs
 * A quick example
 *
 *  +-----------------------+
 *  |  Shift register chain |
 *  +-----------------------+
 *    BL[i .. i + sqrt(N)]     
 *     |
 *     |    CLB[1][H]
 *     |   +---------+     
 *     |   |  SRAM   |     
 *     +-->|  [0..N] |     
 *     |   +---------+
 *     |
 *    ...
 *     |    CLB[1][1]
 *     |   +---------+     
 *     |   |  SRAM   |     
 *     +-->|  [0..N] |     
 *     |   +---------+
 *     |
 * @note optional BL/WL is applicable to WLR, which may not always exist
 */
static 
void add_top_module_nets_cmos_ql_memory_bank_bl_shift_register_bank_bls(ModuleManager& module_manager,
                                                                        const ModuleId& top_module,
                                                                        const MemoryBankShiftRegisterBanks& sr_banks,
                                                                        const std::string sr_blwl_port_name,
                                                                        const std::string& child_blwl_port_name,
                                                                        const bool& optional_blwl = false) {
  for (const ConfigRegionId& config_region : module_manager.regions(top_module)) {
    /* Iterate over each shift register banks */
    for (const auto& bank : sr_banks.bl_banks(config_region)) {
      /* Get the module and instance ids */
      ModuleId sr_bank_module = sr_banks.bl_shift_register_bank_module(config_region, bank);
      size_t sr_bank_instance = sr_banks.bl_shift_register_bank_instance(config_region, bank);
      VTR_ASSERT(sr_bank_module);

      ModulePortId sr_module_blwl_port = module_manager.find_module_port(sr_bank_module, sr_blwl_port_name);
      if (!sr_module_blwl_port && optional_blwl) {
        continue;
      }
      VTR_ASSERT(sr_module_blwl_port);
      BasicPort sr_module_blwl_port_info = module_manager.module_port(sr_bank_module, sr_module_blwl_port);

      for (const BasicPort& src_port : sr_banks.bl_shift_register_bank_source_ports(config_region, bank)) {
        VTR_ASSERT(1 == src_port.get_width());
        /* Create net */
        ModuleNetId net = create_module_source_pin_net(module_manager, top_module,
                                                       sr_bank_module, sr_bank_instance,
                                                       sr_module_blwl_port,
                                                       src_port.pins()[0]);
        VTR_ASSERT(ModuleNetId::INVALID() != net);

        for (size_t ichild = 0; ichild < sr_banks.bl_shift_register_bank_sink_child_ids(config_region, bank, src_port).size(); ++ichild) {
          size_t child_id = sr_banks.bl_shift_register_bank_sink_child_ids(config_region, bank, src_port)[ichild];
          ModuleId child_module = module_manager.region_configurable_children(top_module, config_region)[child_id];
          size_t child_instance = module_manager.region_configurable_child_instances(top_module, config_region)[child_id];

          /* Find the BL port */
          ModulePortId child_blwl_port = module_manager.find_module_port(child_module, child_blwl_port_name);
          BasicPort child_blwl_port_info = module_manager.module_port(child_module, child_blwl_port);

          /* Add net sink */
          size_t sink_child_pin_id = sr_banks.bl_shift_register_bank_sink_child_pin_ids(config_region, bank, src_port)[ichild];
          module_manager.add_module_net_sink(top_module, net,
                                             child_module, child_instance, child_blwl_port, sink_child_pin_id);
        }
      }
    }
  }
}


/************************************************************** 
 * Add BL/WL nets from shift register module to each configurable child
 * BL pins of shift register module are connected to the BL input pins of each PB/CB/SB
 * For all the PB/CB/SB in the same column, they share the same set of BLs
 * A quick example
 *
 *  +-----------------------+
 *  |  Shift register chain |
 *  +-----------------------+
 *    BL[i .. i + sqrt(N)]     
 *     |
 *     |    CLB[1][H]
 *     |   +---------+     
 *     |   |  SRAM   |     
 *     +-->|  [0..N] |     
 *     |   +---------+
 *     |
 *    ...
 *     |    CLB[1][1]
 *     |   +---------+     
 *     |   |  SRAM   |     
 *     +-->|  [0..N] |     
 *     |   +---------+
 *     |
 * @note optional BL/WL is applicable to WLR, which may not always exist
 */
static 
void add_top_module_nets_cmos_ql_memory_bank_wl_shift_register_bank_wls(ModuleManager& module_manager,
                                                                        const ModuleId& top_module,
                                                                        const MemoryBankShiftRegisterBanks& sr_banks,
                                                                        const std::string sr_blwl_port_name,
                                                                        const std::string& child_blwl_port_name,
                                                                        const bool& optional_blwl = false) {
  for (const ConfigRegionId& config_region : module_manager.regions(top_module)) {
    /* Iterate over each shift register banks */
    for (const auto& bank : sr_banks.wl_banks(config_region)) {
      /* Get the module and instance ids */
      ModuleId sr_bank_module = sr_banks.wl_shift_register_bank_module(config_region, bank);
      size_t sr_bank_instance = sr_banks.wl_shift_register_bank_instance(config_region, bank);
      VTR_ASSERT(sr_bank_module);

      ModulePortId sr_module_blwl_port = module_manager.find_module_port(sr_bank_module, sr_blwl_port_name);
      if (!sr_module_blwl_port && optional_blwl) {
        continue;
      }
      VTR_ASSERT(sr_module_blwl_port);
      BasicPort sr_module_blwl_port_info = module_manager.module_port(sr_bank_module, sr_module_blwl_port);

      for (const BasicPort& src_port : sr_banks.wl_shift_register_bank_source_ports(config_region, bank)) {
        VTR_ASSERT(1 == src_port.get_width());
        /* Create net */
        ModuleNetId net = create_module_source_pin_net(module_manager, top_module,
                                                       sr_bank_module, sr_bank_instance,
                                                       sr_module_blwl_port,
                                                       src_port.pins()[0]);
        VTR_ASSERT(ModuleNetId::INVALID() != net);

        for (size_t ichild = 0; ichild < sr_banks.wl_shift_register_bank_sink_child_ids(config_region, bank, src_port).size(); ++ichild) {
          size_t child_id = sr_banks.wl_shift_register_bank_sink_child_ids(config_region, bank, src_port)[ichild];
          ModuleId child_module = module_manager.region_configurable_children(top_module, config_region)[child_id];
          size_t child_instance = module_manager.region_configurable_child_instances(top_module, config_region)[child_id];

          /* Find the BL port */
          ModulePortId child_blwl_port = module_manager.find_module_port(child_module, child_blwl_port_name);
          BasicPort child_blwl_port_info = module_manager.module_port(child_module, child_blwl_port);

          /* Add net sink */
          size_t sink_child_pin_id = sr_banks.wl_shift_register_bank_sink_child_pin_ids(config_region, bank, src_port)[ichild];
          module_manager.add_module_net_sink(top_module, net,
                                             child_module, child_instance, child_blwl_port, sink_child_pin_id);
        }
      }
    }
  }
}

/*********************************************************************
 * This function to add nets for quicklogic memory banks using shift registers to manipulate BL/WLs
 * Each configuration region has independent BL/WL shift register banks
 * - Find the number of BLs and WLs required for each region
 * - Find the number of BL and WL shift register chains required for each region
 * - Create the module of shift register chain for each unique size 
 * - Create nets to connect from top-level module inputs to BL/WL shift register chains
 * - Create nets to connect from BL/WL shift register chains to BL/WL of configurable children
 *
 * @note this function only adds the BL protocol
 *
 * Detailed schematic of each memory bank:
 * @note The numbers are just made to show a simplified example, practical cases are more complicated!
 *
 *                                  sr_clk               sr_head     sr_tail
 *                                    |                   |             ^
 *                                    v                   v             |
 *                           +-------------------------------------------------+                  
 *                           |         Bit Line shift register chains          |
 *                           +-------------------------------------------------+                  
 *                               |              |                    | 
 *    +---------+              BL[0:9]        BL[10:17]          BL[18:22]
 *    |         |                |              |                    | 
 *    |         |                |              |                    |
 *    | Word    |--WL[0:3]-->-----------+---------------+----  ...   |------+-->        
 *    |         |                |      |       |       |            |      |
 *    | Line    |                |      v       |       v            |      v
 *    |         |                |   +-------+  |   +-------+        |  +------+
 *    | shift   |                +-->| SRAM  |  +-->| SRAM  |        +->| SRAM |
 *    |         |                |   | [0:8] |  |   | [0:5] |   ...  |  | [0:7]|
 *    | register|                |   +-------+  |   +-------+        |  +------+
 *    |         |                |              |                    |
 *    | chains  |--WL[4:14]  -----------+--------------+---------    | -----+-->        
 *    |         |                |      |       |       |            |      |
 *    |         |                |      v       |       v            |      v
 *    |         |                |   +-------+  |   +-------+        |  +-------+
 *    |         |                +-->| SRAM  |  |   | SRAM  |        +->| SRAM  |
 *    |         |                |   | [0:80]|  |   | [0:63]|   ...  |  | [0:31]|
 *    |         |                |   +-------+  |   +-------+        |  +-------+
 *    |         |                |                                   |
 *    |         |                |     ...     ...    ...            |    ...
 *    |         |                |              |                    |
 *    |         |--WL[15:18] -----------+---------------+----  ---   | -----+-->        
 *    |         |                |      |       |       |            |      |
 *    |         |                |      v       |       v            |      v
 *    +---------+                |   +-------+  |   +-------+        |  +-------+
 *                               +-->| SRAM  |  +-->| SRAM  |        +->| SRAM  |
 *                               |   |[0:5]  |  |   | [0:8] |   ...  |  | [0:2] |
 *                               |   +-------+  |   +-------+        |  +-------+
 *                               v              v                    v
 *                             WL[0:9]          WL[0:7]                WL[0:4]
 *               
 **********************************************************************/
static 
void add_top_module_nets_cmos_ql_memory_bank_bl_shift_register_config_bus(ModuleManager& module_manager,
                                                                          MemoryBankShiftRegisterBanks& sr_banks,
                                                                          const ModuleId& top_module,
                                                                          const CircuitLibrary& circuit_lib,
                                                                          const ConfigProtocol& config_protocol) {
  CircuitModelId sram_model = config_protocol.memory_model();
  CircuitModelId bl_memory_model = config_protocol.bl_memory_model();
  /* Find out the unique shift register chain sizes */
  std::vector<size_t> unique_sr_sizes = sr_banks.bl_bank_unique_sizes();

  /* Build submodules for shift register chains */ 
  for (const size_t& sr_size : unique_sr_sizes) {
    std::string sr_module_name = generate_bl_shift_register_module_name(circuit_lib.model_name(bl_memory_model), sr_size); 
    build_bl_shift_register_chain_module(module_manager,
                                         circuit_lib,
                                         sr_module_name,
                                         bl_memory_model,
                                         sr_size);
  }

  /* Instanciate the shift register chains in the top-level module */ 
  for (const ConfigRegionId& config_region : module_manager.regions(top_module)) {
    for (const FabricBitLineBankId& sr_bank : sr_banks.bl_banks(config_region)) {
      size_t bl_bank_size = sr_banks.bl_bank_size(config_region, sr_bank);
      std::string sr_module_name = generate_bl_shift_register_module_name(circuit_lib.model_name(bl_memory_model), bl_bank_size); 
      ModuleId sr_bank_module = module_manager.find_module(sr_module_name);
      VTR_ASSERT(sr_bank_module);

      size_t cur_inst = module_manager.num_instance(top_module, sr_bank_module);
      module_manager.add_child_module(top_module, sr_bank_module);

      sr_banks.link_bl_shift_register_bank_to_module(config_region, sr_bank, sr_bank_module);
      sr_banks.link_bl_shift_register_bank_to_instance(config_region, sr_bank, cur_inst);
    }
  }

  for (const ConfigRegionId& config_region : module_manager.regions(top_module)) {
    /************************************************************** 
     * Precompute the BLs and WLs distribution across the FPGA fabric
     * The distribution is a matrix which contains the starting index of BL/WL for each column or row
     */
    std::pair<int, int> child_x_range = compute_memory_bank_regional_configurable_child_x_range(module_manager, top_module, config_region);
    std::map<int, size_t> num_bls_per_tile = compute_memory_bank_regional_bitline_numbers_per_tile(module_manager, top_module,
                                                                                                   config_region,
                                                                                                   circuit_lib, sram_model);
    std::map<int, size_t> bl_start_index_per_tile = compute_memory_bank_regional_blwl_start_index_per_tile(child_x_range, num_bls_per_tile);

    for (size_t child_id = 0; child_id < module_manager.region_configurable_children(top_module, config_region).size(); ++child_id) {
      ModuleId child_module = module_manager.region_configurable_children(top_module, config_region)[child_id];
      vtr::Point<int> coord = module_manager.region_configurable_child_coordinates(top_module, config_region)[child_id]; 

      /* Find the BL port */
      ModulePortId child_bl_port = module_manager.find_module_port(child_module, std::string(MEMORY_BL_PORT_NAME));
      BasicPort child_bl_port_info = module_manager.module_port(child_module, child_bl_port);

      size_t cur_bl_index = 0;

      for (const size_t& sink_bl_pin : child_bl_port_info.pins()) {
        /* Find the shift register bank id for the driving BL port */
        size_t bl_pin_id = bl_start_index_per_tile[coord.x()] + cur_bl_index;
        BasicPort src_bl_port(std::string(MEMORY_BL_PORT_NAME), bl_pin_id, bl_pin_id);
        FabricBitLineBankId sr_bank = sr_banks.find_bl_shift_register_bank_id(config_region, src_bl_port);
        BasicPort sr_bank_port = sr_banks.find_bl_shift_register_bank_data_port(config_region, src_bl_port);
        VTR_ASSERT(sr_bank_port.is_valid());

        sr_banks.add_bl_shift_register_bank_sink_node(config_region, sr_bank, sr_bank_port, child_id, sink_bl_pin);

        cur_bl_index++;
      }
    }
  }

  /* Create connections between top-level module and the BL shift register banks 
   * - Connect the head port from top-level module to each shift register bank
   * - Connect the tail port from each shift register bank to top-level module
   */
  add_top_module_nets_cmos_ql_memory_bank_bl_shift_register_bank_heads(module_manager, top_module, sr_banks);

  add_top_module_nets_cmos_ql_memory_bank_bl_shift_register_bank_tails(module_manager, top_module, sr_banks);

  /* Create connections between BLs of top-level module and BLs of child modules for each region */
  add_top_module_nets_cmos_ql_memory_bank_bl_shift_register_bank_bls(module_manager, top_module, sr_banks,
                                                                     std::string(BL_SHIFT_REGISTER_CHAIN_BL_OUT_NAME),
                                                                     std::string(MEMORY_BL_PORT_NAME));
}

/*********************************************************************
 * Top-level function to add nets for quicklogic memory banks using shift registers to control BL/WLs
 *
 * @note this function only adds the WL configuration bus
 *
 * @note see detailed explanation on the bus connection in function add_top_module_nets_cmos_ql_memory_bank_bl_shift_register_config_bus()
 **********************************************************************/
static 
void add_top_module_nets_cmos_ql_memory_bank_wl_shift_register_config_bus(ModuleManager& module_manager,
                                                                          MemoryBankShiftRegisterBanks& sr_banks,
                                                                          const ModuleId& top_module,
                                                                          const CircuitLibrary& circuit_lib,
                                                                          const ConfigProtocol& config_protocol) {
  CircuitModelId sram_model = config_protocol.memory_model();
  CircuitModelId wl_memory_model = config_protocol.wl_memory_model();
  /* Find out the unique shift register chain sizes */
  std::vector<size_t> unique_sr_sizes = sr_banks.wl_bank_unique_sizes();

  /* Build submodules for shift register chains */ 
  for (const size_t& sr_size : unique_sr_sizes) {
    std::string sr_module_name = generate_wl_shift_register_module_name(circuit_lib.model_name(wl_memory_model), sr_size); 
    build_wl_shift_register_chain_module(module_manager,
                                         circuit_lib,
                                         sr_module_name,
                                         wl_memory_model,
                                         sr_size);
  }

  /* Instanciate the shift register chains in the top-level module */ 
  for (const ConfigRegionId& config_region : module_manager.regions(top_module)) {
    for (const FabricWordLineBankId& sr_bank : sr_banks.wl_banks(config_region)) {
      size_t wl_bank_size = sr_banks.wl_bank_size(config_region, sr_bank);
      std::string sr_module_name = generate_wl_shift_register_module_name(circuit_lib.model_name(wl_memory_model), wl_bank_size); 
      ModuleId sr_bank_module = module_manager.find_module(sr_module_name);
      VTR_ASSERT(sr_bank_module);

      size_t cur_inst = module_manager.num_instance(top_module, sr_bank_module);
      module_manager.add_child_module(top_module, sr_bank_module);

      sr_banks.link_wl_shift_register_bank_to_module(config_region, sr_bank, sr_bank_module);
      sr_banks.link_wl_shift_register_bank_to_instance(config_region, sr_bank, cur_inst);
    }
  }

  for (const ConfigRegionId& config_region : module_manager.regions(top_module)) {
    /************************************************************** 
     * Precompute the BLs and WLs distribution across the FPGA fabric
     * The distribution is a matrix which contains the starting index of BL/WL for each column or row
     */
    std::pair<int, int> child_y_range = compute_memory_bank_regional_configurable_child_y_range(module_manager, top_module, config_region);
    std::map<int, size_t> num_wls_per_tile = compute_memory_bank_regional_wordline_numbers_per_tile(module_manager, top_module,
                                                                                                    config_region,
                                                                                                    circuit_lib, sram_model);
    std::map<int, size_t> wl_start_index_per_tile = compute_memory_bank_regional_blwl_start_index_per_tile(child_y_range, num_wls_per_tile);

    for (size_t child_id = 0; child_id < module_manager.region_configurable_children(top_module, config_region).size(); ++child_id) {
      ModuleId child_module = module_manager.region_configurable_children(top_module, config_region)[child_id];
      vtr::Point<int> coord = module_manager.region_configurable_child_coordinates(top_module, config_region)[child_id]; 

      size_t cur_wl_index = 0;

      /* Find the WL port */
      ModulePortId child_wl_port = module_manager.find_module_port(child_module, std::string(MEMORY_WL_PORT_NAME));
      BasicPort child_wl_port_info = module_manager.module_port(child_module, child_wl_port);

      for (const size_t& sink_wl_pin : child_wl_port_info.pins()) {
        size_t wl_pin_id = wl_start_index_per_tile[coord.y()] + cur_wl_index;
        BasicPort src_wl_port(std::string(MEMORY_WL_PORT_NAME), wl_pin_id, wl_pin_id);

        FabricWordLineBankId sr_bank = sr_banks.find_wl_shift_register_bank_id(config_region, src_wl_port);
        BasicPort sr_bank_port = sr_banks.find_wl_shift_register_bank_data_port(config_region, src_wl_port);
        VTR_ASSERT(sr_bank_port.is_valid());

        sr_banks.add_wl_shift_register_bank_sink_node(config_region, sr_bank, sr_bank_port, child_id, sink_wl_pin);

        cur_wl_index++;
      }
    }
  }

  /* Create connections between top-level module and the BL shift register banks 
   * - Connect the head port from top-level module to each shift register bank
   * - Connect the tail port from each shift register bank to top-level module
   */
  add_top_module_nets_cmos_ql_memory_bank_wl_shift_register_bank_heads(module_manager, top_module, sr_banks);

  add_top_module_nets_cmos_ql_memory_bank_wl_shift_register_bank_tails(module_manager, top_module, sr_banks);

  /* Create connections between BLs of top-level module and BLs of child modules for each region */
  add_top_module_nets_cmos_ql_memory_bank_wl_shift_register_bank_wls(module_manager, top_module, sr_banks,
                                                                     std::string(WL_SHIFT_REGISTER_CHAIN_WL_OUT_NAME),
                                                                     std::string(MEMORY_WL_PORT_NAME));
  add_top_module_nets_cmos_ql_memory_bank_wl_shift_register_bank_wls(module_manager, top_module, sr_banks,
                                                                     std::string(WL_SHIFT_REGISTER_CHAIN_WLR_OUT_NAME),
                                                                     std::string(MEMORY_WLR_PORT_NAME),
                                                                     true);
}

/*********************************************************************
 * Top-level function to add nets for quicklogic memory banks
 * - Each configuration region has independent memory bank circuitry
 * - BL and WL may have different circuitry and wire connection, e.g., decoder, flatten or shift-registers
 * - BL control circuitry
 *   - Decoder: Add a BL decoder; Connect enable, address and data-in (din) between top-level and decoders; Connect data ports between between the decoder and configurable child modules
 *   - Flatten: Connect BLs between the top-level port and configurable child modules
 *   - TODO: Shift registers: add blocks of shift register chain (could be multi-head); Connect shift register outputs to configurable child modules
 *
 * - WL control circuitry
 *   - Decoder: Add a WL decoder; Connect address ports between top-level and decoders; Connect data ports between the decoder and configurable child modules
 *   - Flatten: Connect BLs between the top-level port and configurable child modules
 *   - TODO: Shift registers: add blocks of shift register chain (could be multi-head); Connect shift register outputs to configurable child modules
 ********************************************************************/
void add_top_module_nets_cmos_ql_memory_bank_config_bus(ModuleManager& module_manager,
                                                        DecoderLibrary& decoder_lib,
                                                        MemoryBankShiftRegisterBanks& blwl_sr_banks,
                                                        const ModuleId& top_module,
                                                        const CircuitLibrary& circuit_lib,
                                                        const ConfigProtocol& config_protocol,
                                                        const TopModuleNumConfigBits& num_config_bits) {
  VTR_ASSERT_SAFE(CONFIG_MEM_QL_MEMORY_BANK == config_protocol.type());
  CircuitModelId sram_model = config_protocol.memory_model();

  switch (config_protocol.bl_protocol_type()) {
    case BLWL_PROTOCOL_DECODER: {
      add_top_module_nets_cmos_ql_memory_bank_bl_decoder_config_bus(module_manager, decoder_lib, top_module, circuit_lib, sram_model, num_config_bits);
      break;
    }
    case BLWL_PROTOCOL_FLATTEN: {
      add_top_module_nets_cmos_ql_memory_bank_bl_flatten_config_bus(module_manager, top_module, circuit_lib, sram_model);
      break;
    }
    case BLWL_PROTOCOL_SHIFT_REGISTER: {
      add_top_module_nets_cmos_ql_memory_bank_bl_shift_register_config_bus(module_manager, blwl_sr_banks, top_module, circuit_lib, config_protocol);
      break;
    }
    default: {
      VTR_LOG_ERROR("Invalid BL protocol");
      exit(1);
    }
  }

  switch (config_protocol.wl_protocol_type()) {
    case BLWL_PROTOCOL_DECODER: {
      add_top_module_nets_cmos_ql_memory_bank_wl_decoder_config_bus(module_manager, decoder_lib, top_module, circuit_lib, sram_model, num_config_bits);
      break;
    }
    case BLWL_PROTOCOL_FLATTEN: {
      add_top_module_nets_cmos_ql_memory_bank_wl_flatten_config_bus(module_manager, top_module, circuit_lib, sram_model);
      break;
    }
    case BLWL_PROTOCOL_SHIFT_REGISTER: {
      add_top_module_nets_cmos_ql_memory_bank_wl_shift_register_config_bus(module_manager, blwl_sr_banks, top_module, circuit_lib, config_protocol);
      break;
    }
    default: {
      VTR_LOG_ERROR("Invalid WL protocol");
      exit(1);
    }
  }
}

/********************************************************************
 * Add a list of ports that are used for SRAM configuration to the FPGA 
 * top-level module
 * - Add ports for BL control circuitry:
 *   - Decoder
 *     - an enable signals
 *     - an BL address port
 *     - a data-in port
 *   - Flatten
 *     - BL ports
 *   - TODO: Shift registers
 *     - Head of shift register chain for BLs
 *     - Tail of shift register chain for BLs
 *
 * - Add ports for WL control circuitry:
 *   - Decoder
 *     - an WL address port
 *     - a Readback port (Optional, only needed when WLR is required)
 *   - Flatten
 *     - WL ports
 *     - WLR ports (Optional)
 *   - TODO: Shift registers
 *     - Head of shift register chain for WLs
 *     - Tail of shift register chain for WLs
 *     - a Readback port (Optional, only needed when WLR is required)
 *
 * @note In this memory decoders, the address size will be computed in a different way than the regular one
 ********************************************************************/
void add_top_module_ql_memory_bank_sram_ports(ModuleManager& module_manager, 
                                              const ModuleId& module_id,
                                              const CircuitLibrary& circuit_lib,
                                              const ConfigProtocol& config_protocol,
                                              const MemoryBankShiftRegisterBanks& blwl_sr_banks,
                                              const TopModuleNumConfigBits& num_config_bits) {
  VTR_ASSERT_SAFE(CONFIG_MEM_QL_MEMORY_BANK == config_protocol.type());
  CircuitModelId sram_model = config_protocol.memory_model();

  switch (config_protocol.bl_protocol_type()) {
    case BLWL_PROTOCOL_DECODER: {
      /* Add enable signals */
      BasicPort en_port(std::string(DECODER_ENABLE_PORT_NAME), 1);
      module_manager.add_port(module_id, en_port, ModuleManager::MODULE_INPUT_PORT);

      /* BL address size is the largest among all the regions */
      size_t bl_addr_size = 0;
      for (const ConfigRegionId& config_region : module_manager.regions(module_id)) {
         bl_addr_size = std::max(bl_addr_size, find_mux_local_decoder_addr_size(num_config_bits[config_region].first));
      }
      BasicPort bl_addr_port(std::string(DECODER_BL_ADDRESS_PORT_NAME), bl_addr_size);
      module_manager.add_port(module_id, bl_addr_port, ModuleManager::MODULE_INPUT_PORT);

      /* Data input should be dependent on the number of configuration regions*/
      BasicPort din_port(std::string(DECODER_DATA_IN_PORT_NAME), config_protocol.num_regions());
      module_manager.add_port(module_id, din_port, ModuleManager::MODULE_INPUT_PORT);
      break;
    }
    case BLWL_PROTOCOL_FLATTEN: {
      /* Each region will have independent BLs */
      for (const ConfigRegionId& config_region : module_manager.regions(module_id)) {
        size_t bl_size = num_config_bits[config_region].first;
        BasicPort bl_port(generate_regional_blwl_port_name(std::string(MEMORY_BL_PORT_NAME), config_region), bl_size);
        module_manager.add_port(module_id, bl_port, ModuleManager::MODULE_INPUT_PORT);
      }
      break;
    }
    case BLWL_PROTOCOL_SHIFT_REGISTER: {
      /* Each region will have independent shift register banks */
      for (const ConfigRegionId& config_region : module_manager.regions(module_id)) {
        size_t num_heads = blwl_sr_banks.bl_banks(config_region).size();
        BasicPort blsr_head_port(generate_regional_blwl_port_name(std::string(BL_SHIFT_REGISTER_CHAIN_HEAD_NAME), config_region), num_heads);
        module_manager.add_port(module_id, blsr_head_port, ModuleManager::MODULE_INPUT_PORT);
        BasicPort blsr_tail_port(generate_regional_blwl_port_name(std::string(BL_SHIFT_REGISTER_CHAIN_TAIL_NAME), config_region), num_heads);
        module_manager.add_port(module_id, blsr_tail_port, ModuleManager::MODULE_OUTPUT_PORT);
      }
      break;
    }
    default: {
      VTR_LOG_ERROR("Invalid BL protocol");
      exit(1);
    }
  }

  switch (config_protocol.wl_protocol_type()) {
    case BLWL_PROTOCOL_DECODER: {
      /* WL address size is the largest among all the regions */
      size_t wl_addr_size = 0;
      for (const ConfigRegionId& config_region : module_manager.regions(module_id)) {
         wl_addr_size = std::max(wl_addr_size, find_mux_local_decoder_addr_size(num_config_bits[config_region].second));
      }
      BasicPort wl_addr_port(std::string(DECODER_WL_ADDRESS_PORT_NAME), wl_addr_size);
      module_manager.add_port(module_id, wl_addr_port, ModuleManager::MODULE_INPUT_PORT);

      /* Optional: If we have WLR port, we should add a read-back port */
      if (!circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_WLR).empty()) {
        BasicPort readback_port(std::string(DECODER_READBACK_PORT_NAME), config_protocol.num_regions());
        module_manager.add_port(module_id, readback_port, ModuleManager::MODULE_INPUT_PORT);
      }
      break;
    }
    case BLWL_PROTOCOL_FLATTEN: {
      /* Each region will have independent WLs */
      for (const ConfigRegionId& config_region : module_manager.regions(module_id)) {
        size_t wl_size = num_config_bits[config_region].second;
        BasicPort wl_port(generate_regional_blwl_port_name(std::string(MEMORY_WL_PORT_NAME), config_region), wl_size);
        module_manager.add_port(module_id, wl_port, ModuleManager::MODULE_INPUT_PORT);

        /* Optional: If we have WLR port, we should add a read-back port */
        if (!circuit_lib.model_ports_by_type(sram_model, CIRCUIT_MODEL_PORT_WLR).empty()) {
          BasicPort wlr_port(generate_regional_blwl_port_name(std::string(MEMORY_WLR_PORT_NAME), config_region), wl_size);
          module_manager.add_port(module_id, wlr_port, ModuleManager::MODULE_INPUT_PORT);
        }
      }
      break;
    }
    case BLWL_PROTOCOL_SHIFT_REGISTER: {
      /* Each region will have independent shift register banks */
      for (const ConfigRegionId& config_region : module_manager.regions(module_id)) {
        size_t num_heads = blwl_sr_banks.wl_banks(config_region).size();
        BasicPort wlsr_head_port(generate_regional_blwl_port_name(std::string(WL_SHIFT_REGISTER_CHAIN_HEAD_NAME), config_region), num_heads);
        module_manager.add_port(module_id, wlsr_head_port, ModuleManager::MODULE_INPUT_PORT);
        BasicPort wlsr_tail_port(generate_regional_blwl_port_name(std::string(WL_SHIFT_REGISTER_CHAIN_TAIL_NAME), config_region), num_heads);
        module_manager.add_port(module_id, wlsr_tail_port, ModuleManager::MODULE_OUTPUT_PORT);
      }
      break;
    }
    default: {
      VTR_LOG_ERROR("Invalid WL protocol");
      exit(1);
    }
  }
}

/********************************************************************
 * Load the shift register bank -related data from fabric key to 
 * the dedicated and unified data structure 
 ********************************************************************/
int load_top_module_shift_register_banks_from_fabric_key(const FabricKey& fabric_key,
                                                         MemoryBankShiftRegisterBanks& blwl_sr_banks) {
  blwl_sr_banks.resize_regions(fabric_key.regions().size());

  /* Load Bit-Line shift register banks */
  for (const auto& region : fabric_key.regions()) {
    blwl_sr_banks.reserve_bl_shift_register_banks(region, fabric_key.bl_banks(region).size());
    for (const auto& bank : fabric_key.bl_banks(region)) {
      FabricBitLineBankId sr_bank = blwl_sr_banks.create_bl_shift_register_bank(region);
      for (const auto& data_port : fabric_key.bl_bank_data_ports(region, bank)) {
        blwl_sr_banks.add_data_port_to_bl_shift_register_bank(region, sr_bank, data_port);
      }
    }
  }

  /* Load Bit-Line shift register banks */
  for (const auto& region : fabric_key.regions()) {
    blwl_sr_banks.reserve_wl_shift_register_banks(region, fabric_key.wl_banks(region).size());
    for (const auto& bank : fabric_key.wl_banks(region)) {
      FabricWordLineBankId sr_bank = blwl_sr_banks.create_wl_shift_register_bank(region);
      for (const auto& data_port : fabric_key.wl_bank_data_ports(region, bank)) {
        blwl_sr_banks.add_data_port_to_wl_shift_register_bank(region, sr_bank, data_port);
      }
    }
  }

  return 0;
}

/********************************************************************
 * @brief This functions synchronize the settings in configuration protocol (from architecture description)
 * and the existing information (loaded from fabric key files)
 * @note This function should be called AFTER load_top_module_shift_register_banks_from_fabric_key()
 ********************************************************************/
void sync_memory_bank_shift_register_banks_with_config_protocol_settings(ModuleManager& module_manager,
                                                                         MemoryBankShiftRegisterBanks& blwl_sr_banks,
                                                                         const ConfigProtocol& config_protocol,
                                                                         const ModuleId& top_module,
                                                                         const CircuitLibrary& circuit_lib) {
  /* ONLY synchronize when the configuration protocol is memory bank using shift registers */
  if ( CONFIG_MEM_QL_MEMORY_BANK != config_protocol.type()
    || BLWL_PROTOCOL_SHIFT_REGISTER != config_protocol.bl_protocol_type()
    || BLWL_PROTOCOL_SHIFT_REGISTER != config_protocol.wl_protocol_type() ) {
    return;
  }

  /* Fabric key has a higher priority in defining the shift register bank organization */
  if (!blwl_sr_banks.empty()) {
    return;
  }

  CircuitModelId sram_model = config_protocol.memory_model();

  /* Reach here, if means we do not have any definition from fabric key files, use the settings from the configuration protocol */
  blwl_sr_banks.resize_regions(module_manager.regions(top_module).size());

  /* Based on the number of shift register banks, evenly distribute the BLs in each region for each shift register bank */
  for (const auto& config_region : module_manager.regions(top_module)) {
    size_t num_bls = compute_memory_bank_regional_num_bls(module_manager, top_module,
                                                          config_region,
                                                          circuit_lib, sram_model);
    size_t num_bl_banks = config_protocol.bl_num_banks();
    blwl_sr_banks.reserve_bl_shift_register_banks(config_region, num_bl_banks);

    size_t regular_sr_bank_size = num_bls / num_bl_banks;
    size_t cur_bl_index = 0;
    for (size_t ibank = 0; ibank < num_bl_banks; ++ibank) {
      /* For last bank, use all the residual sizes */
      size_t cur_sr_bank_size = regular_sr_bank_size;
      if (ibank == num_bl_banks - 1) {
        cur_sr_bank_size = num_bls - ibank * regular_sr_bank_size; 
      }
      /* Create a bank and assign data ports */
      FabricBitLineBankId bank = blwl_sr_banks.create_bl_shift_register_bank(config_region);
      BasicPort data_ports(std::string(MEMORY_BL_PORT_NAME), cur_bl_index, cur_bl_index + cur_sr_bank_size - 1);
      blwl_sr_banks.add_data_port_to_bl_shift_register_bank(config_region, bank, data_ports);
     
      /* Increment the bl index */
      cur_bl_index += cur_sr_bank_size;
    }

    VTR_ASSERT(cur_bl_index == num_bls);
  }

  /* Based on the number of shift register banks, evenly distribute the WLs in each region for each shift register bank */
  for (const auto& config_region : module_manager.regions(top_module)) {
    size_t num_wls = compute_memory_bank_regional_num_wls(module_manager, top_module,
                                                          config_region,
                                                          circuit_lib, sram_model);
    size_t num_wl_banks = config_protocol.wl_num_banks();
    blwl_sr_banks.reserve_wl_shift_register_banks(config_region, num_wl_banks);

    size_t regular_sr_bank_size = num_wls / num_wl_banks;
    size_t cur_wl_index = 0;
    for (size_t ibank = 0; ibank < num_wl_banks; ++ibank) {
      /* For last bank, use all the residual sizes */
      size_t cur_sr_bank_size = regular_sr_bank_size;
      if (ibank == num_wl_banks - 1) {
        cur_sr_bank_size = num_wls - ibank * regular_sr_bank_size; 
      }
      /* Create a bank and assign data ports */
      FabricWordLineBankId bank = blwl_sr_banks.create_wl_shift_register_bank(config_region);
      BasicPort data_ports(std::string(MEMORY_WL_PORT_NAME), cur_wl_index, cur_wl_index + cur_sr_bank_size - 1);
      blwl_sr_banks.add_data_port_to_wl_shift_register_bank(config_region, bank, data_ports);
     
      /* Increment the bl index */
      cur_wl_index += cur_sr_bank_size;
    }

    VTR_ASSERT(cur_wl_index == num_wls);
  }
}

} /* end namespace openfpga */
